package gotask

//  一个极度简单的任务调度, 用来设计做简单巡逻处理(可以添加循环任务,也可以添加一次性任务)
//   单协程运作,
//   1. 注意回调不要处理占用太多时间.
//   2. id 如果相同则, 不会重复添加
//
//update
//  2023-07-04 08:59:25
//	删除任务后，清理id, cb, args参数， 确保引用的id得到释放

import (
	"errors"
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/panicsafe"
	"sync/atomic"
	"time"
)

type patrolcb func(id interface{}, args ...interface{})

type patrolItem struct {
	busyflag    int32
	id          interface{}
	minInterval time.Duration
	cb          patrolcb
	args        []interface{}
	lastExec    time.Time
}

func (this *patrolItem) cleanup() {
	this.id = nil
	this.cb = nil
	this.args = nil
}

// 确保和移除, 添加在同一个协程, 否则要做并发互斥处理
func (this *patrolItem) checkDo() bool {
	if panicsafe.GoFunCatchException {
		defer panicsafe.DeferCatchPanic()
	}

	if atomic.CompareAndSwapInt32(&this.busyflag, 0, 1) {
		defer atomic.StoreInt32(&this.busyflag, 0)
		if this.lastExec.IsZero() || time.Since(this.lastExec) >= this.minInterval {
			this.cb(this.id, this.args...)
			this.lastExec = time.Now()
			return true
		}
	}

	return false
}

func (this *patrolItem) unpack(args []interface{}) {
	this.minInterval = args[2].(time.Duration)
	this.cb = args[3].(patrolcb)
	this.args = args[4].([]interface{})
}

type PatrolTask struct {
	startFlag    int32
	timeout      time.Duration
	cmdChan      chan interface{}
	lst          []*patrolItem
	innerDo_flag int8
	lastDo_t     time.Time
}

func NewPatrolTask() *PatrolTask {
	rval := &PatrolTask{timeout: time.Second}
	rval.cmdChan = make(chan interface{}, 1024)
	rval.lst = make([]*patrolItem, 0, 1024)
	return rval
}

func (this *PatrolTask) Status() string {
	return fmt.Sprintf("task:%d, lastdo_t:[%d],%s", len(this.lst), this.innerDo_flag, gobase.DateTimeString3(this.lastDo_t))
}

func (this *PatrolTask) CheckStart() {
	if atomic.CompareAndSwapInt32(&this.startFlag, 0, 1) {
		go func() {
			if panicsafe.GoFunCatchException {
				defer panicsafe.DeferCatchPanic()
			}
			defer func() {
				this.startFlag = 0
			}()
			this.innerFor()
		}()
	}
}

func (this *PatrolTask) innerPushCmd(args ...interface{}) error {
	select {
	case this.cmdChan <- args:
	case <-time.After(this.timeout):
		return errors.New("PatrolTask:push cmd chan timeout!")
	}
	return nil
}

func (this *PatrolTask) innerProcessCmd(cmd interface{}) {
	args := cmd.([]interface{})
	iCmd := args[0].(int)
	if iCmd == 0 { // 添加
		id := args[1]
		for i := 0; i < len(this.lst); i++ {
			itm := this.lst[i]
			if itm.id == id {
				itm.unpack(args)
				return
			}
		}

		itm := &patrolItem{id: id}
		itm.lastExec = time.Now()
		itm.unpack(args)
		this.lst = append(this.lst, itm)
	} else if iCmd == 1 { // 删除
		id := args[1]
		for i := 0; i < len(this.lst); i++ {
			itm := this.lst[i]
			if itm.id == id {
				itm.cleanup()
				this.lst = append(this.lst[:i], this.lst[i+1:]...)
				return
			}
		}
	} else if iCmd == 10 { // 一次性
		id := args[1]
		args[2].(patrolcb)(id, args[3].([]interface{})...)
	} else if iCmd == 11 { // 一次性
		fn := args[1].(func(arg interface{}))
		fn(args[2])
	}
}

func (this *PatrolTask) AddTask(id interface{}, minInterval time.Duration, fn patrolcb, args ...interface{}) error {
	return this.innerPushCmd(0, id, minInterval, fn, args)
}

func (this *PatrolTask) DelTask(id interface{}) error {
	return this.innerPushCmd(1, id)
}

func (this *PatrolTask) AddTaskOnce(id interface{}, fn patrolcb, args ...interface{}) error {
	return this.innerPushCmd(10, id, fn, args)
}

func (this *PatrolTask) AddTaskOnceA1(arg interface{}, fn func(arg interface{})) error {
	return this.innerPushCmd(11, fn, arg)
}

func (this *PatrolTask) innerDo() {
	this.innerDo_flag = 1
	this.lastDo_t = time.Now()
	defer func() {
		this.innerDo_flag = 0
	}()
	for i := 0; i < len(this.lst); i++ {
		itm := this.lst[i]
		itm.checkDo()
	}
}

func (this *PatrolTask) CheckAbnormal() error {
	if this.innerDo_flag == 1 {
		ms := time.Since(this.lastDo_t).Milliseconds()
		if ms > 1000 {
			return fmt.Errorf("innerDo执行了(%d)ms", ms)
		}
	}
	return nil
}

func (this *PatrolTask) innerFor() {
	ticker := time.NewTicker(time.Millisecond * 100)
	for {
		select {
		case cmd := <-this.cmdChan:
			if cmd == nil {
				break
			}
			this.innerProcessCmd(cmd)
		case <-ticker.C:
			this.innerDo()
		}
	}
}

var (
	defaultPatrolTaskCreateFlag int32 = 0
	defaultPatrolTask           *PatrolTask
)

func checkDefaultPatrol() bool {
	if defaultPatrolTask == nil {
		if atomic.CompareAndSwapInt32(&defaultPatrolTaskCreateFlag, 0, 1) {
			defaultPatrolTask = NewPatrolTask()
			defaultPatrolTask.CheckStart()
		} else {
			time.Sleep(time.Millisecond * 100)
		}
	}
	return defaultPatrolTask != nil
}

func DefaultPatrolTask() *PatrolTask {
	if !checkDefaultPatrol() {
		panic("DefaultPatrolTask创建失败!!!")
	}
	return defaultPatrolTask
}

func AddPatrolTask(id interface{}, minInterval time.Duration, fn patrolcb, args ...interface{}) {

}
